Skip to content

fix(cli): scope skill installs to the agents in use, not all ~70 via --all#1748

Merged
WaterrrForever merged 1 commit into
mainfrom
fix/skills-agent-targeting
Jun 26, 2026
Merged

fix(cli): scope skill installs to the agents in use, not all ~70 via --all#1748
WaterrrForever merged 1 commit into
mainfrom
fix/skills-agent-targeting

Conversation

@WaterrrForever

Copy link
Copy Markdown
Collaborator

What

hyperframes skills, hyperframes skills update, and hyperframes init no longer pass --all to the upstream skills add. A new resolveAgentTargets (packages/cli/src/utils/skillsTargets.ts) decides which agents an install targets, and runSkillsAdd — the single chokepoint all three paths funnel through (installAllSkillsrunSkillsAdd) — builds --skill '*' --agent <resolved…> --yes from it.

Effect: an install lands in the agent(s) the project (or machine) actually uses, instead of all ~70 agent conventions the upstream CLI knows about.

Why

--all is upstream shorthand for --skill '*' --agent '*' -y — every skill into every one of the ~70 agent conventions. A user who only runs Claude Code got skill folders for Cursor, Codex, and 50+ tools they never touch: noise in the project, and skills written to directories nothing reads.

This is the targeting complement to the recent skills work: detection is sound (#1738), update reconciles removals (#1740 / #1743), installs are byte-faithful and correctly placed (#1744 --copy) — but the agent fan-out was still universal. This scopes it.

How — how we pick the target agents

resolveAgentTargets, in priority order:

  1. The project already has agent skill folders (.claude/skills, .hermes/skills, …) → install only to those. An existing folder is the strongest signal of intent, so we honour it exactly and add nothing else.
  2. Blank project:
    1. Running under Claude Code (CLAUDECODE) → just claude-code.
    2. Else probe PATH for installed agent CLIs (claude, hermes, droid, cursor, codex, opencode, gemini) — an executable on PATH means that agent is actually installed here.
    3. Else fall back to a floor of claude-code + the shared .agents universal dir, which Cursor, Codex, OpenCode, Gemini, Copilot and ~14 others read from in project scope. Never --agent '*'.

The dir↔key map is explicit because host-dir names differ from upstream agent keys (.factorydroid, .hermeshermes-agent) and many agents share .agents (→ the single universal key). Keys verified against vercel-labs/skills@v1.5.13.

--skill '*' (all skills) and --copy (faithful, skills check-detectable — #1744) are unchanged; only the agent fan-out is scoped. The install prints its chosen targets and the reason.

Test plan

  • Unit — pure resolver (skillsTargets.test.ts): every branch (existing folders → keys, .factorydroid / .kirokiro-cli / .agentsuniversal mapping, CLAUDECODE, PATH detection, universal-bucket collapse, the .claude+.agents floor), existing-folders-beat-env precedence, and a "never returns '*'" invariant. buildSkillsAddArgs shape pinned.

  • Unit — command args (skills.test.ts): the spawn builds --skill '*' --agent … --yes --copy, never --all, and the value after --agent is never the '*' wildcard (regression guard). Existing GIT_CLONE_PROTECTION / prune-scope / update-exit-code tests still pass.

  • End-to-end (isolated project + HOME, real upstream skills@1.5.13, --copy):

    project state result host folders
    blank .claude/skills (19) + .agents/skills (19) 2, not 50+
    only .hermes/skills pre-seeded .hermes/skills (19) only — no .claude, no .agents 1
  • Full CLI suite green (bunx vitest run packages/cli — 1030 pass; the 2 *.browser.test.ts errors are a local happy-dom resolution quirk under ad-hoc vitest, unrelated).

  • oxlint, oxfmt --check, tsc, and fallow all clean (lefthook pre-commit passed).

Docs follow-up (separate): README.md, docs/quickstart.mdx, docs/guides/prompting.mdx, and docs/guides/skills.mdx still tell users to run npx skills add heygen-com/hyperframes --all directly. That's the raw upstream CLI path (it bypasses hyperframes), so it still sprays — worth updating in its own change.

🤖 Generated with Claude Code

`hyperframes skills`, `skills update`, and `init` all shelled out to
`skills add ... --all`, which the upstream CLI expands to
`--skill '*' --agent '*' -y` — every skill into every one of the ~70 agent
conventions it knows about. A user who only runs Claude Code got skill
folders for Cursor, Codex, and 50+ tools they never touch.

Replace `--all` with a resolved target set (new resolveAgentTargets):

  1. If the project already has agent skill folders (`.claude/skills`,
     `.hermes/skills`, …), install ONLY to those — an existing folder is the
     strongest signal of intent, so honour it exactly.
  2. Otherwise (blank project):
     a. Running under Claude Code (CLAUDECODE) → just claude-code.
     b. Else probe PATH for installed agent CLIs (claude, hermes, droid,
        cursor, codex, opencode, gemini) — the gstack approach.
     c. Else fall back to claude-code + the shared `.agents` universal dir,
        which Cursor, Codex, OpenCode, Gemini, Copilot and ~14 others read
        from in project scope. Never `--agent '*'`.

Installs stay `--skill '*'` (all skills) + `--copy` (faithful, detectable by
`skills check`); only the agent fan-out is scoped. The dir<->key map is
explicit because dir names differ from upstream keys (`.factory`/droid,
`.hermes`/hermes-agent) and many agents share `.agents` (-> the single
`universal` key). Keys verified against vercel-labs/skills@v1.5.13.

Verified end-to-end in an isolated project + HOME: the new args land exactly
`.claude/skills` (19) + `.agents/skills` (19) and nothing else — 2 folders,
not 50+. Pure resolver covered by skillsTargets.test.ts; command-arg shape and
the "never --all" regression pinned in skills.test.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — no blockers. Nicely-scoped robustness fix, and it directly closes the "handle all agents like skills add / gstack" concern raised on #1738. Verified independently:

  1. Upstream flags valid at the pinned vercel-labs/skills@v1.5.13-a, --agent, -s, --skill, -y, --yes all exist (src/cli.ts), and --all is literally shorthand for --skill '*' --agent '*' -y. So replacing --all with --skill '*' --agent <resolved> --yes is a correct narrowing, not a behavior guess.
  2. resolveAgentTargets never resolves to empty — existing project skill folders (honor intent) → CLAUDECODE → PATH-probe of installed agent CLIs (the gstack approach) → floor (claude-code + .agents universal). The floor fallback is the key safety net so a blank machine still gets a sane target. DIR_TO_KEY keys verified against v1.5.13; the explicit dir↔key mapping (.factorydroid, .hermeshermes-agent) and the OpenClaw exclusion (bare skills/ collision) are thoughtful.
  3. Composes correctly with #1744's --copyrunSkillsAdd still appends --copy after the resolved args; explicit extraArgs still wins for callers/tests; the prior installAllSkills({extraArgs:['--all','--yes']}) caller is updated to use resolution.

Good coverage (skillsTargets.test.ts +130 + skills.test.ts), and the Installing to: … log is nice transparency.

— Jerrai

@miga-heygen miga-heygen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — fix(cli): scope skill installs to relevant agents

SHA: 7d28f29
Verdict: LGTM — clean, well-reasoned change. A few observations below, nothing blocking.


What this does (my read)

The PR replaces every --all (= --skill '*' --agent '*' -y) occurrence in the skills install pipeline with a resolver (resolveAgentTargets) that picks only the agents the project or machine actually uses. The resolver lives in a new skillsTargets.ts utility with a clear priority chain: existing project folders > CLAUDECODE env > PATH-detected CLIs > a claude-code + universal floor. The buildSkillsAddArgs helper produces --skill '*' --agent <targets...> --yes — verified against the upstream parseAddOptions which consumes variadic args after --agent until it hits another flag.

Correctness

  1. DIR_TO_KEY mapping — verified against vercel-labs/skills@v1.5.13 agents.ts: .claude -> claude-code, .agents -> universal, .hermes -> hermes-agent, .factory -> droid, .kiro -> kiro-cli. All correct.

  2. Upstream --agent parsing — the upstream parseAddOptions grabs variadic non-flag args after --agent, so --agent claude-code universal --yes parses as agent: ["claude-code", "universal"], yes: true. The generated arg order from buildSkillsAddArgs is correct.

  3. init.ts cleanup — the old code had a CLAUDECODE branch that passed ["--all", "--agent", "claude-code", "--yes"] and a fallback of ["--all", "--yes"]. The new code delegates entirely to resolveAgentTargets, which handles the CLAUDECODE case (step 2a) and applies the same logic init was hand-rolling. Good simplification — the special-casing was duplicated between init and skills, now it's in one place.

  4. skills update path — previously passed ["--all", "--yes"] as extraArgs, now passes no extraArgs so runSkillsAdd resolves targets itself. The installAllSkillsrunSkillsAdd chain correctly falls through to resolveAgentTargets when opts.extraArgs is undefined. Clean.

  5. extraArgs escape hatchrunSkillsAdd still respects an explicit extraArgs when passed, so callers that need specific targeting can bypass the resolver. Good for forward compatibility.

Observations (non-blocking)

  1. isDir is slightly over-cautious — it calls both existsSync and statSync, but statSync already throws ENOENT when the path doesn't exist, so the existsSync check is redundant. The try/catch around both handles it fine though, so this is just a nit — no functional impact.

  2. PATH probe creates empty files as "executables" in the test helper (pathWith) — works on Linux/macOS because existsSync doesn't check the execute bit. On Windows the test also probes .exe/.cmd/.bat extensions, but the test helper doesn't create those suffixed files. This is fine because the tests run on Linux and the Windows path is an unlikely CI target, but worth noting if you ever add Windows test matrix.

  3. The DETECTABLE list is conservative — only 7 CLIs are probed. Agents like goose, continue, kilo, devin, etc. are omitted. This seems intentional (the comment says "the gstack approach"), and the universal floor covers agents that read .agents/skills anyway. Just flagging for awareness — if a user has only goose installed, they'd get the claude-code + universal floor rather than the goose key. Since goose uses .goose/skills (not .agents/skills), the universal bucket wouldn't reach it. This is an edge case and arguably the right call for now — expanding the list can be a follow-up.

  4. keysForDirs iteration order — relies on Object.entries(DIR_TO_KEY) preserving insertion order (spec-guaranteed for string keys in V8/modern engines). The comment "claude-code first" in the test matches the object literal order. Solid.

  5. Test coverage — good coverage of all resolver branches: existing folders, dir-to-key mapping, CLAUDECODE env, PATH detection, universal-bucket collapse, floor fallback, the "never *" invariant, and buildSkillsAddArgs shape. The skills.test.ts additions pin the spawn args and add a "never --all" regression guard. The mock of resolveAgentTargets in skills.test.ts is the right call — isolates command-arg construction from environment-dependent resolution.

CI

Several checks still pending/skipping at time of review (Typecheck, Build, CLI smoke, Test, etc.). No failures observed.

Overall this is a well-structured change with clear reasoning, good test coverage, and correct integration with the upstream CLI. Ship it.

— Miga

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at 7d28f291. No blockers.

What I verified:

  • packages/cli/src/commands/skills.ts:57 keeps runSkillsAdd as the single install chokepoint and replaces the default --all spray with concrete --skill "*" --agent <resolved> --yes args while preserving explicit extraArgs for callers that intentionally bypass resolution.
  • packages/cli/src/utils/skillsTargets.ts:128 resolves targets in the right order: existing project skill folders first, then CLAUDECODE, then PATH-detected installed agent CLIs, then the claude-code + universal floor, so it never emits the * wildcard or an empty target set.
  • Upstream vercel-labs/skills@v1.5.13 accepts the generated argv shape: parseAddOptions consumes multiple non-flag values after --agent, and the mapped keys (claude-code, universal, hermes-agent, droid, kiro-cli) match upstream agents.ts.
  • packages/cli/src/utils/skillsTargets.test.ts:33 plus packages/cli/src/commands/skills.test.ts:200 cover the resolver branches and pin the no---all / no-wildcard regression.

Additive to Rames + Miga: I do not see a blocking gap. Their notes on isDir, the conservative detectable CLI list, and Windows-suffixed test helpers are non-blocking.

CI note: all completed checks were green at review time; Tests on windows-latest was still pending, so merge should wait for that check if required.

Verdict: APPROVE
Reasoning: This narrows the install target set without changing the upstream install contract, and the resolver has a safe floor plus focused tests for the no-spray behavior.

— Magi

@WaterrrForever WaterrrForever merged commit 70a03f7 into main Jun 26, 2026
41 checks passed
@WaterrrForever WaterrrForever deleted the fix/skills-agent-targeting branch June 26, 2026 19:45
WaterrrForever added a commit that referenced this pull request Jun 26, 2026
Resolve the skills-distribution conflicts. main already carried the
manifest/version-check + prune work (#1738/#1740/#1743) and the #1748
"scope installs via skillsTargets" approach; this branch reworks install
to the global symlink-mirror model, so:

- Keep main's prune/removed-detection (skillsManifest.ts, the rm + update
  prune path, the related tests) verbatim; re-apply only the global-first
  locateInstall on top.
- Replace #1748's skillsTargets-based scoped install with the global
  --copy + --full-depth install + mirrorGlobalSkills fan-out; delete the
  now-superseded skillsTargets.ts (+ test).
- Keep main's 0.7.13 version bump + add the gen:agent-dirs script.
- Regenerate skills-manifest.json against the merged skills/ tree.

Verified: typecheck, oxlint, oxfmt, fallow gate all clean; 1044 CLI tests
pass (main's prune suite + the new mirror/global suite together).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants